/*
  lrz - receive files with x/y/zmodem
  Copyright (C) until 1988 Chuck Forsberg (Omen Technology INC)
  Copyright (C) 1994 Matt Porter, Michael D. Black
  Copyright (C) 1996, 1997 Uwe Ohse
  Copyright (C) 2018 Michael L. Gran

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.

  originally written by Chuck Forsberg
*/

#include "zglobal.h"

#define SS_NORMAL 0
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>
#include <string.h>

#include "log.h"
#include "zmodem.h"
#include "crctab.h"
#include "zm.h"
#include "zreadline.h"

#define MAX_BLOCK (2048)

const char *program_name;		/* the name by which we were called */

struct rz_ {
	zm_t *zm;		/* Zmodem comm primitives' state. */
	// Workspaces
	char attn[ZATTNLEN+1];  /* Attention string rx sends to tx on err */
	char secbuf[MAX_BLOCK + 1]; /* Workspace to store up to 8k
				     * blocks */
	// Dynamic state
	FILE *fout;		/* FP to output file. */
	int lastrx;		/* Either 0, or CAN if last receipt
				 * was sender-cancelled */
	int firstsec;
	int errors;		/* Count of read failures */
	int skip_if_not_found;  /* When true, the operation opens and
				 * appends to an existing file. */
	int thisbinary;		/* When > 0, current file is to be
				 * received in bin mode */
	char zconv;		/* ZMODEM file conversion request. */
	char zmanag;		/* ZMODEM file management request. */
	char ztrans;		/* SET BUT UNUSED: ZMODEM file transport
				 * request byte */
	int tryzhdrtype;         /* Header type to send corresponding
				  * to Last rx close */

	// Constant
	int topipe;		/* A flag. When true, open the file as a pipe. */
	int try_resume; /* A flag. When true, try restarting downloads */

	size_t rx_bytes; /* 统计接收的字节数 */
};

typedef struct rz_ rz_t;

rz_t *rz_init(int fd, size_t readnum, size_t bufsize, int no_timeout,
	      int rxtimeout, int znulls, int eflag, int baudrate, int zctlesc, int zrwindow,
	      int try_resume,
	      int topipe
);


static int rz_receive_files (rz_t *rz, struct zm_fileinfo *);
static int rz_zmodem_session_startup (rz_t *rz);
static void report (int sct);
static void uncaps (char *s);
static int IsAnyLower (const char *s);
static int rz_write_string_to_file (rz_t *rz, struct zm_fileinfo *zi, char *buf, size_t n);
static int rz_process_header (rz_t *rz, char *name, struct zm_fileinfo *);
static int rz_receive_sector (rz_t *rz, size_t *Blklen, char *rxbuf, unsigned int maxtime);
static int rz_receive_sectors (rz_t *rz, struct zm_fileinfo *);
static int rz_receive_pathname (rz_t *rz, struct zm_fileinfo *, char *rpn);
static int rz_receive (rz_t *rz);
static int rz_receive_file (rz_t *rz, struct zm_fileinfo *);
static int rz_closeit (rz_t *rz, struct zm_fileinfo *);
static void write_modem_escaped_string_to_stdout (const char *s);
static size_t getfree (void);

static rz_t g_rz;
rz_t*
rz_init(int fd, size_t readnum, size_t bufsize, int no_timeout,
	    int rxtimeout, int znulls, int eflag, int baudrate, int zctlesc, int zrwindow,
		int try_resume,
		int topipe
	)
{
	rz_t *rz = &g_rz;
	memset (rz, 0, sizeof(rz_t));
	rz->zm = zm_init(fd, readnum, bufsize, no_timeout,
			 rxtimeout, znulls, eflag, baudrate, zctlesc, zrwindow);
	rz->zconv = 0;
	rz->try_resume = try_resume;
	rz->fout = NULL;
	rz->topipe = topipe;
	rz->errors = 0;
	rz->tryzhdrtype=ZRINIT;
	rz->skip_if_not_found = FALSE;
	rz->rx_bytes = 0;
	return rz;
}

/*
 * Let's receive something already.
 */
size_t zmodem_receive(uint32_t flash_addr, uint32_t flags)
{
	//log_set_level(LOG_ERROR);
	rz_t *rz = rz_init(0, /* fd */
			   8192, /* readnum */
			   16384, /* bufsize */
			   1, /* no_timeout */
			   100,		 /* rxtimeout */
			   0,		 /* znulls */
			   0,		 /* eflag */
			   2400,	 /* baudrate */
			   0,		 /* zctlesc */
			   1400,	 /* zrwindow */
			   0,		 /* try_resume */
			   0		 /* topipe */
		);
	rz->zm->baudrate = io_mode(0, 1);
	rz->fout = (FILE *)flash_addr; // 为了减少工作量，保留FILE类型，在使用时强转为相应类型
	int exitcode = 0;
	if (rz_receive(rz)==ERROR) {
		exitcode=0200;
		zreadline_canit(rz->zm->zr, STDOUT_FILENO);
	}
	io_mode(0,0);
	if (exitcode && !rz->zm->zmodem_requested)
		zreadline_canit(rz->zm->zr, STDOUT_FILENO);
	if (exitcode)
		log_info( "Transfer incomplete\r\n");
	else
		log_info("Transfer complete\r\n");

	return rz->rx_bytes;
}


static int
rz_receive(rz_t *rz)
{
	int c;
	struct zm_fileinfo zi;
	zi.fname=NULL;
	zi.modtime=0;
	zi.mode=0;
	zi.bytes_total=0;
	zi.bytes_sent=0;
	zi.bytes_received=0;
	zi.bytes_skipped=0;
	zi.eof_seen=0;

	log_info("%s waiting to receive.\r\n", program_name);
	c = 0;
	c = rz_zmodem_session_startup(rz);
	if (c != 0) {
		if (c == ZCOMPL)
			return OK;
		if (c == ERROR)
			goto fubar;
		c = rz_receive_files(rz, &zi);

		if (c)
			goto fubar;
	} else {
		for (;;) {
			//timing(1,NULL);
			if (rz_receive_pathname(rz, &zi, rz->secbuf)== ERROR)
				goto fubar;
			if (rz->secbuf[0]==0)
				return OK;
			if (rz_process_header(rz, rz->secbuf, &zi) == ERROR)
				goto fubar;
			if (rz_receive_sectors(rz, &zi)==ERROR)
				goto fubar;
		}
	}
	return OK;
fubar:
	zreadline_canit(rz->zm->zr, STDOUT_FILENO);
	return ERROR;
}


/*
 * Fetch a pathname from the other end as a C ctyle ASCIZ string.
 * Length is indeterminate as long as less than Blklen
 */
static int
rz_receive_pathname(rz_t *rz, struct zm_fileinfo *zi, char *rpn)
{
	register int c;
	size_t Blklen=0;		/* record length of received packets */

	zreadline_getc(rz->zm->zr, 1);

et_tu:
	rz->firstsec=TRUE;
	zi->eof_seen=FALSE;
	putchar(WANTCRC);
	fflush(stdout);
	zreadline_flushline(rz->zm->zr); /* Do read next time ... */
	while ((c = rz_receive_sector(rz, &Blklen, rpn, 100)) != 0) {
		if (c == WCEOT) {
			log_error( "Pathname fetch returned EOT\r\n");
			putchar(ACK);
			fflush(stdout);
			zreadline_flushline(rz->zm->zr);	/* Do read next time ... */
			zreadline_getc(rz->zm->zr, 1);
			goto et_tu;
		}
		return ERROR;
	}
	putchar(ACK);
	fflush(stdout);
	return OK;
}

/*
 * Adapted from CMODEM13.C, written by
 * Jack M. Wierda and Roderick W. Hart
 */
static int
rz_receive_sectors(rz_t *rz, struct zm_fileinfo *zi)
{
	register int sectnum, sectcurr;
	register char sendchar;
	size_t Blklen;

	rz->firstsec=TRUE;sectnum=0;
	zi->eof_seen=FALSE;
	sendchar=WANTCRC;

	for (;;) {
		putchar(sendchar);	/* send it now, we're ready! */
		fflush(stdout);
		zreadline_flushline(rz->zm->zr);	/* Do read next time ... */
		sectcurr=rz_receive_sector(rz, &Blklen, rz->secbuf,
			(unsigned int) ((sectnum&0177) ? 50 : 130));
		report(sectcurr);
		if (sectcurr==((sectnum+1) &0377)) {
			sectnum++;
			if (zi->bytes_total && R_BYTESLEFT(zi) < Blklen)
				Blklen=R_BYTESLEFT(zi);
			zi->bytes_received+=Blklen;
			if (rz_write_string_to_file(rz, zi, rz->secbuf, Blklen)==ERROR)
				return ERROR;
			sendchar=ACK;
		}
		else if (sectcurr==(sectnum&0377)) {
			log_error( "Received dup Sector\r\n");
			sendchar=ACK;
		}
		else if (sectcurr==WCEOT) {
			if (rz_closeit(rz, zi))
				return ERROR;
			putchar(ACK);
			fflush(stdout);
			zreadline_flushline(rz->zm->zr);	/* Do read next time ... */
			return OK;
		}
		else if (sectcurr==ERROR)
			return ERROR;
		else {
			log_error( "Sync Error\r\n");
			return ERROR;
		}
	}
}

/*
 * Rz_Receive_Sector fetches a Ward Christensen type sector.
 * Returns sector number encountered or ERROR if valid sector not received,
 * or CAN CAN received
 * or WCEOT if eot sector
 * time is timeout for first char, set to 4 seconds thereafter
 ***************** NO ACK IS SENT IF SECTOR IS RECEIVED OK **************
 *    (Caller must do that when he is good and ready to get next sector)
 */
static int
rz_receive_sector(rz_t *rz, size_t *Blklen, char *rxbuf, unsigned int maxtime)
{
	register int checksum, wcj, firstch;
	register unsigned short oldcrc;
	register char *p;
	int sectcurr;

	rz->lastrx = 0;
	for (rz->errors = 0; rz->errors < RETRYMAX; rz->errors++) {

		if ((firstch=zreadline_getc(rz->zm->zr, maxtime))==STX) {
			*Blklen=1024; goto get2;
		}
		if (firstch==SOH) {
			*Blklen=128;
get2:
			sectcurr=zreadline_getc(rz->zm->zr, 1);
			if ((sectcurr+(oldcrc=zreadline_getc(rz->zm->zr, 1)))==0377) {
				oldcrc=checksum=0;
				for (p=rxbuf,wcj=*Blklen; --wcj>=0; ) {
					if ((firstch=zreadline_getc(rz->zm->zr, 1)) < 0)
						goto bilge;
					oldcrc=updcrc(firstch, oldcrc);
					checksum += (*p++ = firstch);
				}
				if ((firstch=zreadline_getc(rz->zm->zr,1)) < 0)
					goto bilge;
				oldcrc=updcrc(firstch, oldcrc);
				if ((firstch=zreadline_getc(rz->zm->zr,1)) < 0)
					goto bilge;
				oldcrc=updcrc(firstch, oldcrc);
				if (oldcrc & 0xFFFF)
					log_error( "CRC\r\n");
				else {
					rz->firstsec=FALSE;
					return sectcurr;
				}
			}
			else
				log_error("Sector number garbled\r\n");
		}
		/* make sure eot really is eot and not just mixmash */
		else if (firstch==EOT && zreadline_getc(rz->zm->zr,1)==TIMEOUT)
			return WCEOT;
		else if (firstch==CAN) {
			if (rz->lastrx==CAN) {
				log_error( "Sender Cancelled\r\n");
				return ERROR;
			} else {
				rz->lastrx=CAN;
				continue;
			}
		}
		else if (firstch==TIMEOUT) {
			if (rz->firstsec)
				goto humbug;
bilge:
			log_error("TIMEOUT\r\n");
		}
		else
			log_error( "Got 0%o sector header\r\n", firstch);

humbug:
		rz->lastrx=0;
		{
			int cnt=1000;
			while(cnt-- && zreadline_getc(rz->zm->zr,1)!=TIMEOUT)
				;
		}
		if (rz->firstsec) {
			putchar(WANTCRC);
			fflush(stdout);
			zreadline_flushline(rz->zm->zr);	/* Do read next time ... */
		} else {
			maxtime=40;
			putchar(NAK);
			fflush(stdout);
			zreadline_flushline(rz->zm->zr);	/* Do read next time ... */
		}
	}
	/* try to stop the bubble machine. */
	zreadline_canit(rz->zm->zr, STDOUT_FILENO);
	return ERROR;
}


/*
 * Process incoming file information header
 */
static int
rz_process_header(rz_t *rz, char *name, struct zm_fileinfo *zi)
{
	char *p;
	static char name_static[20];
	char *nameend;
	
	strcpy(name_static, name);
	zi->fname=name_static;

	log_debug("zmanag=%d\r\n", rz->zmanag);
	log_debug("zconv=%d\r\n",rz->zconv);

	/* set default parameters and overrides */
	rz->thisbinary = TRUE;

	/*
	 *  Process ZMODEM remote file management requests
	 */
	if (rz->zconv == ZCNL)	/* Remote ASCII override */
		rz->thisbinary = 0;
	if (rz->zconv == ZCBIN)	/* Remote Binary override */
		rz->thisbinary = TRUE;
	if (rz->zconv == ZCBIN && rz->try_resume)
		rz->zconv=ZCRESUM;

	zi->bytes_total = DEFBYTL;
	zi->mode = 0;
	zi->eof_seen = 0;
	zi->modtime = 0;

	nameend = name + 1 + strlen(name);
	if (*nameend) {	/* file coming from Unix or DOS system */
		long modtime;
		long bytes_total;
		int mode;
		sscanf(nameend, "%ld%lo%o", &bytes_total, &modtime, &mode);
		zi->modtime=modtime;
		zi->bytes_total=bytes_total;
		zi->mode=mode;
		if (zi->mode & UNIXFILE)
			++rz->thisbinary;
	}
	
	if(!*nameend)
	{		/* File coming from CP/M system */
		for (p=name_static; *p; ++p)		/* change / to _ */
			if ( *p == '/')
				*p = '_';

		if ( *--p == '.')		/* zap trailing period */
			*p = 0;
	}

	if (!rz->zm->zmodem_requested && !IsAnyLower(name_static)
	  && !(zi->mode&UNIXFILE))
		uncaps(name_static);

	zi->bytes_received=zi->bytes_skipped;

	return OK;
}


/*
 * Rz_Write_String_To_File writes the n characters of buf to receive file fout.
 *  If not in binary mode, carriage returns, and all characters
 *  starting with CPMEOF are discarded.
 */
static int
rz_write_string_to_file(rz_t *rz, struct zm_fileinfo *zi, char *buf, size_t n)
{
	char *p;

	if (n == 0)
		return OK;
	if (rz->thisbinary) {
		if (fwrite2(buf,n,1,rz->fout) == 0)
		{
			return ERROR;
		}

		rz->rx_bytes += n;
		rz->fout = (FILE *)((uint32_t)rz->fout + n);
	}
	else {
		if (zi->eof_seen)
			return OK;
		for (p=buf; n>0; ++p,n-- ) {
			if ( *p == '\r')
				continue;
			if (*p == CPMEOF) {
				zi->eof_seen=TRUE;
				return OK;
			}
			putc2(*p ,rz->fout);
		}
	}
	return OK;
}

/* make string s lower case */
static void
uncaps(char *s)
{
	for ( ; *s; ++s)
		if (isupper((unsigned char)(*s)))
			*s = tolower(*s);
}
/*
 * IsAnyLower returns TRUE if string s has lower case letters.
 */
static int
IsAnyLower(const char *s)
{
	for ( ; *s; ++s)
		if (islower((unsigned char)(*s)))
			return TRUE;
	return FALSE;
}

static void
report(int sct)
{
	log_debug("Blocks received: %d\r\n",sct);
}

/*
 * Initialize for Zmodem receive attempt, try to activate Zmodem sender
 *  Handles ZSINIT frame
 *  Return ZFILE if Zmodem filename received, -1 on error,
 *   ZCOMPL if transaction finished,  else 0
 */
static int
rz_zmodem_session_startup(rz_t *rz)
{
	register int c, n;
	int zrqinits_received=0;
	size_t bytes_in_block=0;

	/* Spec 8.1: "When the ZMODEM receive program starts, it
	   immediately sends a ZRINIT header to initiate ZMODEM file
	   transfers...  The receive program resends its header at
	   intervals for a suitable period of time (40 seconds
	   total)...."

	   On startup rz->tryzhdrtype is, by default, set to ZRINIT
	*/

	for (n=rz->zm->zmodem_requested?15:5;
		 (--n + zrqinits_received) >=0 && zrqinits_received<10; ) {
		/* Set buffer length (0) and capability flags */

		/*
		 * We're going to snd a ZRINIT packet.
		 * 第3个参数告诉发送端一次发送多少字节数据，起始值33B，每增加1增加256B，最高1024
		 * 例如：0:33B, 1:289B, 2:545B
		*/
		zm_set_header_payload_bytes(rz->zm,
#ifdef CANBREAK
					    (rz->zm->zctlesc ?
					     (CANFC32|CANFDX|CANOVIO|CANBRK|TESCCTL)
					     : (CANFC32|CANFDX|CANOVIO|CANBRK)),
#else
					    (rz->zm->zctlesc ?
					     (CANFC32|TESCCTL)
					     : (CANFC32)),
#endif
					    4, 0, 0);
		zm_send_hex_header(rz->zm, rz->tryzhdrtype);
		if (rz->tryzhdrtype == ZSKIP)	/* Don't skip too far */
			rz->tryzhdrtype = ZRINIT;	/* CAF 8-21-87 */
again:
		switch (zm_get_header(rz->zm, NULL)) {
		case ZRQINIT:

			/* Spec 8.1: "[after sending ZRINIT] if the
			 * receiving program receives a ZRQINIT
			 * header, it resends the ZRINIT header." */

			/* getting one ZRQINIT is totally ok. Normally a ZFILE follows
			 * (and might be in our buffer, so don't flush it). But if we
			 * get more ZRQINITs than the sender has started up before us
			 * and sent ZRQINITs while waiting.
			 */
			zrqinits_received++;
			continue;

		case ZEOF:
			continue;
		case TIMEOUT:
			continue;
		case ZFILE:
			rz->zconv = rz->zm->Rxhdr[ZF0];
			if (!rz->zconv)
				/* resume with sz -r is impossible (at least with unix sz)
				 * if this is not set */
				rz->zconv=ZCBIN;
			if (rz->zm->Rxhdr[ZF1] & ZF1_ZMSKNOLOC) {
				rz->zm->Rxhdr[ZF1] &= ~(ZF1_ZMSKNOLOC);
				rz->skip_if_not_found=TRUE;
			}
			rz->zmanag = rz->zm->Rxhdr[ZF1];
			rz->ztrans = rz->zm->Rxhdr[ZF2];
			rz->tryzhdrtype = ZRINIT;
			c = zm_receive_data(rz->zm, rz->secbuf, MAX_BLOCK,&bytes_in_block);
			rz->zm->baudrate = io_mode(0,3);
			if (c == GOTCRCW)
				return ZFILE;
			zm_send_hex_header(rz->zm, ZNAK);
			goto again;
		case ZSINIT:
			/* Spec 8.1: "[after receiving the ZRINIT]
			 * then sender may then send an optional
			 * ZSINIT frame to define the receiving
			 * program's Attn sequence, or to specify
			 * complete control character escaping.  If
			 * the ZSINIT header specified ESCCTL or ESC8,
			 * a HEX header is used, and the receiver
			 * activates the specified ESC modes before
			 * reading the following data subpacket.  */

			/* this once was:
			 * Zctlesc = TESCCTL & zm->Rxhdr[ZF0];
			 * trouble: if rz get --escape flag:
			 * - it sends TESCCTL to sz,
			 *   get a ZSINIT _without_ TESCCTL (yeah - sender didn't know),
			 *   overwrites Zctlesc flag ...
			 * - sender receives TESCCTL and uses "|=..."
			 * so: sz escapes, but rz doesn't unescape ... not good.
			 */
			rz->zm->zctlesc |= (TESCCTL & rz->zm->Rxhdr[ZF0]);
			if (zm_receive_data(rz->zm, rz->attn, ZATTNLEN, &bytes_in_block) == GOTCRCW) {
				/* Spec 8.1: "[after receiving a
				 * ZSINIT] the receiver sends a ZACK
				 * header in response, containing
				 * either the serial number of the
				 * receiving program, or 0." */
				zm_set_header_payload(rz->zm, 1L);
				zm_send_hex_header(rz->zm, ZACK);
				goto again;
			}
			zm_send_hex_header(rz->zm, ZNAK);
			goto again;
		case ZFREECNT:
			zm_set_header_payload(rz->zm, getfree());
			zm_send_hex_header(rz->zm, ZACK);
			goto again;
		case ZCOMPL:
			goto again;
		default:
			continue;
		case ZFIN:
			zm_ackbibi(rz->zm);
			return ZCOMPL;
		case ZRINIT:
			/* Spec 8.1: "If [after sending ZRINIT] the
			   receiving program receives a ZRINIT header,
			   it is an echo indicating that the sending
			   program is not operational."  */
			log_info("got ZRINIT\r\n");
			return ERROR;
		case ZCAN:
			log_info("got ZCAN\r\n");
			return ERROR;
		}
	}
	return 0;
}


/*
 * Receive 1 or more files with ZMODEM protocol
 */
static int
rz_receive_files(rz_t *rz, struct zm_fileinfo *zi)
{
	register int c;

	for (;;) {
		//timing(1,NULL);
		c = rz_receive_file(rz, zi);
		switch (c) {
		case ZEOF:
		{
		}
		/* FALL THROUGH */
		case ZSKIP:
			if (c==ZSKIP)
			{
				log_info("Skipped\r\n");
			}
			switch (rz_zmodem_session_startup(rz)) {
			case ZCOMPL:
				return OK;
			default:
				return ERROR;
			case ZFILE:
				break;
			}
			continue;
		default:
			return c;
		case ERROR:
			return ERROR;
		}
	}
}

/* "OOSB" means Out Of Sync Block. I once thought that if sz sents
 * blocks a,b,c,d, of which a is ok, b fails, we might want to save
 * c and d. But, alas, i never saw c and d.
 */
typedef struct oosb_t {
	size_t pos;
	size_t len;
	char *data;
	struct oosb_t *next;
} oosb_t;
struct oosb_t *anker=NULL;

/*
 * Receive a file with ZMODEM protocol
 *  Assumes file name frame is in rz->secbuf
 */
static int
rz_receive_file(rz_t *rz, struct zm_fileinfo *zi)
{
	register int c, n;
	size_t bytes_in_block=0;

	zi->eof_seen=FALSE;

	n = 20;

	if (rz_process_header(rz, rz->secbuf,zi) == ERROR) {
		return (rz->tryzhdrtype = ZSKIP);
	}

	for (;;) {
		zm_set_header_payload(rz->zm, zi->bytes_received);
		zm_send_hex_header(rz->zm, ZRPOS);
		goto skip_oosb;
nxthdr:	
skip_oosb:
		c = zm_get_header(rz->zm, NULL);
		switch (c) {
		default:
			log_debug("rz_receive_file: zm_get_header returned %d\r\n", c);
			return ERROR;
		case ZNAK:
		case TIMEOUT:
			if ( --n < 0) {
				log_debug("rz_receive_file: zm_get_header returned %d\r\n", c);
				return ERROR;
			}
		case ZFILE:
			zm_receive_data(rz->zm, rz->secbuf, MAX_BLOCK,&bytes_in_block);
			continue;
		case ZEOF:
			if (zm_reclaim_receive_header(rz->zm) != (long) zi->bytes_received) {
				/*
				 * Ignore eof if it's at wrong place - force
				 *  a timeout because the eof might have gone
				 *  out before we sent our zrpos.
				 */
				rz->errors = 0;
				goto nxthdr;
			}
			if (rz_closeit(rz, zi)) {
				rz->tryzhdrtype = ZFERR;
				log_debug("rz_receive_file: rz_closeit returned <> 0\r\n");
				return ERROR;
			}
			log_debug("rz_receive_file: normal EOF\r\n");
			return c;
		case ERROR:	/* Too much garbage in header search error */
			if ( --n < 0) {
				log_debug("rz_receive_file: zm_get_header returned %d\r\n", c);
				return ERROR;
			}
			write_modem_escaped_string_to_stdout(rz->attn);
			continue;
		case ZSKIP:
			rz_closeit(rz, zi);
			log_debug("rz_receive_file: Sender SKIPPED file\r\n");
			return c;
		case ZDATA:
			if (zm_reclaim_receive_header(rz->zm) != (long) zi->bytes_received) {
				size_t pos=zm_reclaim_receive_header(rz->zm);
				if ( --n < 0) {
					log_debug("rz_receive_file: out of sync\r\n");
					return ERROR;
				}
				switch (c = zm_receive_data(rz->zm, rz->secbuf, MAX_BLOCK,&bytes_in_block))
				{
				case GOTCRCW:
				case GOTCRCG:
				case GOTCRCE:
				case GOTCRCQ:
					break;				
				}
				write_modem_escaped_string_to_stdout(rz->attn);  continue;
			}
moredata:
			switch (c = zm_receive_data(rz->zm, rz->secbuf, MAX_BLOCK,&bytes_in_block))
			{
			case ZCAN:
				log_debug("rz_receive_file: zm_receive_data returned %d\r\n", c);
				return ERROR;
			case ERROR:	/* CRC error */
				if ( --n < 0) {
					log_debug("rz_receive_file: zm_get_header returned %d\r\n", c);
					return ERROR;
				}
				write_modem_escaped_string_to_stdout(rz->attn);
				continue;
			case TIMEOUT:
				if ( --n < 0) {
					log_debug("rz_receive_file: zm_get_header returned %d\r\n", c);
					return ERROR;
				}
				continue;
			case GOTCRCW:
				n = 20;
				rz_write_string_to_file(rz, zi, rz->secbuf, bytes_in_block);
				zi->bytes_received += bytes_in_block;
				zm_set_header_payload(rz->zm, zi->bytes_received);
				zm_send_hex_header(rz->zm, ZACK | 0x80);
				goto nxthdr;
			case GOTCRCQ:
				n = 20;
				rz_write_string_to_file(rz, zi, rz->secbuf, bytes_in_block);
				zi->bytes_received += bytes_in_block;
				zm_set_header_payload(rz->zm, zi->bytes_received);
				zm_send_hex_header(rz->zm, ZACK);
				goto moredata;
			case GOTCRCG:
				n = 20;
				rz_write_string_to_file(rz, zi, rz->secbuf, bytes_in_block);
				zi->bytes_received += bytes_in_block;
				goto moredata;
			case GOTCRCE:
				n = 20;
				rz_write_string_to_file(rz, zi, rz->secbuf, bytes_in_block);
				zi->bytes_received += bytes_in_block;
				goto nxthdr;
			}
		}
	}
}

/*
 * Send a string to the modem, processing for \336 (sleep 1 sec)
 *   and \335 (break signal)
 */
static void
write_modem_escaped_string_to_stdout(const char *s)
{
	const char *p;

	while (s && *s)
	{
		p=strpbrk(s,"\335\336");
		if (!p)
		{
			putdatas(s,strlen(s));
			return;
		}
		if (p!=s)
		{
			putdatas(s,(size_t) (p-s));
			s=p;
		}
		if (*p=='\336')
			sleep(1);
		else
			sendbrk(0);
		p++;
	}
}

/*
 * Close the receive dataset, return OK or ERROR
 */
static int
rz_closeit(rz_t *rz, struct zm_fileinfo *zi)
{
	return OK;
}
/*
 * Routine to calculate the free bytes on the current file system
 *  ~0 means many free bytes (unknown)
 */
static size_t
getfree(void)
{
	return((size_t) (~0L));	/* many free bytes ... */
}

/* End of lrz.c */
